﻿using Autofac;
using Autofac.Extensions.DependencyInjection;
using Autofac.Extras.DynamicProxy;
using AutoMapper;
using Blog.Common.Helper;
using Blog.Common.Redis;
using Blog.Core.AOP;
using Blog.Core.Base;
using Blog.Core.IRepository.Base;
using Blog.Core.IServices;
using Blog.Core.Repository.sugar;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using Swashbuckle.AspNetCore.Swagger;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Blog.Core
{
    public class Startup
    {
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="configuration"></param>
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
            // 修改输出为xml格式
            //services.AddMvc(
            //    options =>
            //    {
            //        options.ReturnHttpNotAcceptable = true;
            //        options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
            //    });

            BaseDBConfig.ConnectionString = Configuration.GetSection("AppSettings:SqlServerConnection").Value;

            //redis注入到容器中
            services.AddScoped<IRedisCacheManager, RedisCacheManager>();//这里说下，如果是自己的项目，个人更建议使用单例模式 services.AddSingleton 

            #region Swagger
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new Info
                {
                    Version = "v0.1.0",
                    Title = "Blog.Core API",
                    Description = "框架说明文档",
                    TermsOfService = "None",
                    Contact = new Swashbuckle.AspNetCore.Swagger.Contact { Name = "Blog.Core", Email = "Blog.Core@xxx.com", Url = "https://www.baidu.com" }
                });
                //var basePath = PlatformServices.Default.Application.ApplicationBasePath;
                var basePath = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath;

                var xmlPath = Path.Combine(basePath, "Blog.Core.xml"); // 这个就是刚刚配置的xml文件名
                c.IncludeXmlComments(xmlPath, true); //默认的第二个参数是false，这个是controller的注释，记得修改

                var xmlModelPath = Path.Combine(basePath, "Blog.Core.Model.xml"); //这个是model层的文件名
                c.IncludeXmlComments(xmlModelPath, true);//默认的第二个参数是false，这个是controller的注释，记得修改

                #region Token绑定到ConfigureServices
                //添加header验证信息
                //c.OperationFilter<SwaggerHeader>();
                var security = new Dictionary<string, IEnumerable<string>> { { "Blog.Core", new string[] { } }, };
                c.AddSecurityRequirement(security);
                //方案名称“Blog.Core”可自定义，上下一致即可
                c.AddSecurityDefinition("Blog.Core", new ApiKeyScheme
                {
                    Description = "JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}（注意两者之间是一个空格）\"",
                    Name = "Authorization",//jwt默认的参数名称
                    In = "header",//jwt默认存放Authorization信息的位置(请求头中)
                    Type = "apiKey"
                });
                #endregion

            });
            //services.AddSwaggerGen(options =>
            //{
            //    options.SwaggerDoc("v1", new Info() { Title = "Swagger Test UI", Version = "v1" });
            //    options.CustomSchemaIds(type => type.FullName); // 解决相同类名会报错的问题
            //    options.IncludeXmlComments(Path.Combine(Directory.GetCurrentDirectory(), "SwaggerUIDemo.xml")); // 标注要使用的 XML 文档
            //});


            ////泛型注册
            //builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>)).InstancePerDependency();

            #endregion

            #region AutoMapper
            services.AddAutoMapper(typeof(Startup));
            #endregion

            #region HttpClientFactory工厂类的注册
            services.AddHttpClient("client_1", config =>  //这里指定的name=client_1，可以方便我们后期服用该实例
            {
                config.BaseAddress = new Uri("http://client_1.com");
                config.DefaultRequestHeaders.Add("header_1", "header_1");
            });
            services.AddHttpClient("client_2", config =>
            {
                config.BaseAddress = new Uri("http://client_2.com");
                config.DefaultRequestHeaders.Add("header_2", "header_2");
            });

            // 注册完全自定义类
            services.AddHttpClient<ISampleClient, SampleClient>();

            services.AddHttpClient();
            #endregion

            #region 控制反转 ,举例说明
            //services.AddTransient<IApplicationService, ApplicationService>;//服务在每次请求时被创建，它最好被用于轻量级无状态服务（如我们的Repository和ApplicationService服务）

            //services.AddScoped<IApplicationService, ApplicationService>;//服务在每次请求时被创建，生命周期横贯整次请求

            //services.AddSingleton<IApplicationService, ApplicationService>;//Singleton（单例） 服务在第一次请求时被创建（或者当我们在ConfigureServices中指定创建某一实例并运行方法），其后的每次请求将沿用已创建服务。如果开发者的应用需要单例服务情景，请设计成允许服务容器来对服务生命周期进行操作，而不是手动实现单例设计模式然后由开发者在自定义类中进行操作。
            #endregion

            //如果你想使用官方认证，必须在上边ConfigureService 中，配置JWT的认证服务 
            services.AddAuthentication().AddJwtBearer(o =>
            {
                o.Events = new JwtBearerEvents()
                {
                    OnMessageReceived = context =>
                    {
                        context.Token = context.Request.Query["access_token"];
                        return Task.CompletedTask;
                    }
                };
                o.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
                {
                    //NameClaimType = JwtClaimTypes.Name,
                    //RoleClaimType = JwtClaimTypes.Role,

                    //ValidIssuer = "http://localhost:5200",
                    //ValidAudience = "api",
                    //IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Consts.Secret))

                    /***********************************TokenValidationParameters的参数默认值***********************************/
                    // RequireSignedTokens = true,
                    // SaveSigninToken = false,
                    // ValidateActor = false,
                    // 将下面两个参数设置为false，可以不验证Issuer和Audience，但是不建议这样做。
                    // ValidateAudience = true,
                    // ValidateIssuer = true, 
                    // ValidateIssuerSigningKey = false,
                    // 是否要求Token的Claims中必须包含Expires
                    // RequireExpirationTime = true,
                    // 允许的服务器时间偏移量
                    // ClockSkew = TimeSpan.FromSeconds(300),
                    // 是否验证Token有效期，使用当前时间与Token的Claims中的NotBefore和Expires对比
                    // ValidateLifetime = true
                };
            });

            //而JwtBearer客户端的配置就更加简单了，因为OIDC具有配置发现的功能：
            //services.AddAuthentication(x =>
            //{
            //    x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            //    x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            //}).AddJwtBearer(o =>
            //   {
            //       o.Authority = "https://oidc.faasx.com/";
            //       o.Audience = "api";
            //       o.TokenValidationParameters = new TokenValidationParameters
            //       {
            //           NameClaimType = JwtClaimTypes.Name,
            //           RoleClaimType = JwtClaimTypes.Role,
            //       };
            //   });


            #region AutoFac
            //实例化 Autofac容器
            var builder = new ContainerBuilder();

            //注册要通过反射创建的组件
            //builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>();

            #region LoadFile和LoadFrom二者的区别

            //Assembly.LoadFile只载入相应的dll文件，比如Assembly.LoadFile("a.dll"); // 则载入a.dll，假如a.dll中引用了b.dll的话，b.dll并不会被载入。
            //Assembly.LoadFrom  //则不一样，它会载入dll文件及其引用的其他dll，比如上面的例子，b.dll也会被载入。
            #endregion

            //var basePath = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath; //获取项目路径
            //var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll"); //获取注入项目绝对路径
            //var assemblysServices = Assembly.LoadFile(servicesDllFile);//直接采用加载文件的方法

            #region 无接口注入
            //var assemblysServices= Assembly.Load("AutoFac.Service");
            //builder.RegisterAssemblyTypes(assemblysServices); //指定已扫描程序中的实现类

            //  var assemblysRepository = Assembly.Load("AutoFac.Repository");
            //builder.RegisterAssemblyTypes(assemblysRepository); //指定已扫描程序中的实现类
            #endregion

            #region CORS
            //跨域第二种方法，声明策略，记得下边app中配置
            services.AddCors(c =>
            {
                //↓↓↓↓↓↓↓注意正式环境不要使用这种全开放的处理↓↓↓↓↓↓↓↓↓↓
                c.AddPolicy("AllRequests", policy =>
                {
                    policy
                    .AllowAnyOrigin()//允许任何源
                    .AllowAnyMethod()//允许任何方式
                    .AllowAnyHeader()//允许任何头
                    .AllowCredentials();//允许cookie
                });
                //↑↑↑↑↑↑↑注意正式环境不要使用这种全开放的处理↑↑↑↑↑↑↑↑↑↑


                //一般采用这种方法
                c.AddPolicy("LimitRequests", policy =>
                {
                    policy
                    .WithOrigins("http://127.0.0.1:1818", "http://localhost:8080", "http://localhost:8021", "http://localhost:8081", "http://localhost:1818")//支持多个域名端口，注意端口号后不要带/斜杆：比如localhost:8000/，是错的
                    .AllowAnyHeader()//Ensures that the policy allows any header.
                    .AllowAnyMethod();
                });
            });

            //注意还需要在下边 Configure方法 中进行中间件配置 这里有歧义，部分读者可能没看懂，请看下边解释
            //services.AddCors();
            #endregion

            #region 单独类的注入

            //public class Love
            //{
            //    // 一定要是虚方法
            //    public virtual string SayLoveU()
            //    {
            //        return "I love U";
            //    }

            //}

            ////只能注入该类中的虚方法
            //builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(Love)))
            //    .EnableClassInterceptors()
            //    .InterceptedBy(typeof(BlogLogAOP));
            #endregion

            #region 多个实现类的注入
            //builder.RegisterType<CategoryHelper>().As<ICategoryHelper>().InstancePerHttpRequest();

            //builder.RegisterType<NewsHelper>().Named<INewsHelper>("news");
            //builder.RegisterType<SubjectHelper>().Named<INewsHelper>("subject");
            #endregion


            //var path = new FileInfo(Assembly.GetExecutingAssembly().Location);
            //var assemblysServices = Assembly.LoadFile(Path.Combine(path.DirectoryName, "Blog.Core.Services.dll"));
            // AOP 开关，如果想要打开指定的功能，只需要在 appsettigns.json 对应对应 true 就行。
            var cacheType = new List<Type>();
            if (Appsettings.app(new string[] { "AppSettings", "RedisCaching", "Enabled" }).ObjToBool())
            {
                cacheType.Add(typeof(BlogRedisCacheAOP));
            }
            if (Appsettings.app(new string[] { "AppSettings", "MemoryCachingAOP", "Enabled" }).ObjToBool())
            {
                cacheType.Add(typeof(BlogCacheAOP));
            }
            if (Appsettings.app(new string[] { "AppSettings", "LogAOP", "Enabled" }).ObjToBool())
            {
                cacheType.Add(typeof(BlogLogAOP));
            }

            builder.RegisterType<BlogLogAOP>();//可以直接替换其他拦截器！一定要把拦截器进行注册

            var assemblysServices = Assembly.Load("Blog.Core.Services"); //要记得!!!这个注入的是实现类层，不是接口层！不是 IServices

            //builder.RegisterAssemblyTypes(assemblysServices).AsImplementedInterfaces(); //指定已扫描程序集中的类型注册为提供所有其实现的接口。


            builder.RegisterAssemblyTypes(assemblysServices)
                      .AsImplementedInterfaces()
                      .InstancePerLifetimeScope()
                      //对目标类型启用接口拦截。拦截器将被确定，通过在类或接口上截取属性, 或添加 
                      .EnableInterfaceInterceptors()//引用Autofac.Extras.DynamicProxy;
                                                    //允许将拦截器服务的列表分配给注册。就是将拦截器添加到要注入容器的接口或者类之上。
                      .InterceptedBy(typeof(BlogLogAOP));//可以直接替换拦截器

            var assemblysRepository = Assembly.Load("Blog.Core.Repository"); ;//模式是 Load(解决方案名)

            builder.RegisterAssemblyTypes(assemblysRepository).AsImplementedInterfaces(); //指定已扫描程序集中的类型注册为提供所有其实现的接口。

            //将service填充到Autofac容器中
            builder.Populate(services);



            //将已进行的组件登记创建新容器
            var ApplicationContainer = builder.Build();
            #endregion


            return new AutofacServiceProvider(ApplicationContainer);  // 第三方IOC接管 core内置DI容器

        }

        // 此方法由运行时调用。 使用此方法配置HTTP请求管道。
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            //判断是否是环境变量
            if (env.IsDevelopment())
            {
                // 在开发环境中，使用异常页面，这样可以暴露错误堆栈信息，所以不要放在生产环境。
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // 在非开发环境中，使用HTTP严格安全传输(or HSTS) 对于保护web安全是非常重要的。
                // 强制实施 HTTPS 在 ASP.NET Core，配合 app.UseHttpsRedirection
                //app.UseHsts();
            }


            #region Swagger
            //app.UseSwagger();
            //app.UseSwaggerUI(c =>
            //{
            //    //之前是写死的
            //    //c.SwaggerEndpoint("/swagger/v1/swagger.json", "ApiHelp V1");
            //    //c.RoutePrefix = "";//路径配置，设置为空，表示直接在根域名（localhost:8001）访问该文件,注意localhost:8001/swagger是访问不到的，去launchSettings.json把launchUrl去掉

            //    //根据版本名称倒序 遍历展示
            //    typeof(ApiVersions).GetEnumNames().OrderByDescending(e => e).ToList().ForEach(version =>
            //    {
            //        c.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"{ApiName} {version}");
            //    });
            //});
            app.UseSwagger();
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "ApiHelp V1");
            });

            #endregion


            #region Authon
            //app.UseMiddleware<JwtTokenAuth>();//注意此授权方法已经放弃，请使用下边的官方验证方法。但是如果你还想传User的全局变量，还是可以继续使用中间件

            // 如果你想使用官方认证，必须在上边ConfigureService 中，配置JWT的认证服务
            // .AddAuthentication 和 .AddJwtBearer 二者缺一不可
            app.UseAuthentication();
            #endregion

            #region CORS
            //跨域第二种方法，使用策略，详细策略信息在ConfigureService中
            app.UseCors("LimitRequests");//将 CORS 中间件添加到 web 应用程序管线中, 以允许跨域请求。

            //自定义认证中间件
            app.UseJwtTokenAuth();


            //跨域第一种版本，请要ConfigureService中配置服务 services.AddCors();
            //    app.UseCors(options => options.WithOrigins("http://localhost:8021").AllowAnyHeader()
            //.AllowAnyMethod()); 

            #endregion

            // 跳转https
            //app.UseHttpsRedirection();
            // 使用静态文件
            app.UseStaticFiles();
            // 使用cookie
            app.UseCookiePolicy();
            // 返回错误码
            app.UseStatusCodePages();//把错误码返回前台，比如是404
            app.UseMiddleware<JwtTokenAuth>();
            app.UseMvc();
        }
    }
}
